﻿/* PN264 - A Parallel H.264 Encoding Utility
 * Copyright © 2009 PN264 Development Team
 *
 * This software is released the terms and conditions of the MIT License,
 * a copy of which can be found in the License.txt file.
 *
 * Contributors:
 * Will 'AnarkiNet' Shelley (AnarkiNet@gmail.com)
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace PN264
{
    /// <summary>
    /// Provides a collection of <see cref="Worker"/> objects.
    /// </summary>
    /// <remarks>
    /// Public static members that modify the state of the <see cref="WorkerPool"/> are guaranteed to be thread-safe.
    /// </remarks>
    public static class WorkerPool
    {
        #region Events
        /// <summary>
        /// Raised when the <see cref="WorkerPool.Capacity"/> property changes.
        /// </summary>
        public static event EventHandler CapacityChanged
        {
            add
            {
                lock (WorkerPool.capacityChangedLock)
                {
                    WorkerPool.capacityChanged += value;
                }
            }
            remove
            {
                lock (WorkerPool.capacityChangedLock)
                {
                    WorkerPool.capacityChanged -= value;
                }
            }
        }
        /// <summary>
        /// Raised when a <see cref="Worker"/> is added to the pool.
        /// </summary>
        public static event EventHandler<WorkerEventArgs> WorkerAdded
        {
            add
            {
                lock (WorkerPool.workerAddedLock)
                {
                    WorkerPool.workerAdded += value;
                }
            }
            remove
            {
                lock (WorkerPool.workerAddedLock)
                {
                    WorkerPool.workerAdded -= value;
                }
            }
        }
        /// <summary>
        /// Raised when a <see cref="Worker"/> is removed from the pool.
        /// </summary>
        public static event EventHandler<WorkerEventArgs> WorkerRemoved
        {
            add
            {
                lock (WorkerPool.workerRemovedLock)
                {
                    WorkerPool.workerRemoved += value;
                }
            }
            remove
            {
                lock (WorkerPool.workerRemovedLock)
                {
                    WorkerPool.workerRemoved -= value;
                }
            }
        }
        #endregion
        #region Fields
        private static int capacity = 1;
        private static EventHandler capacityChanged;
        private static readonly object capacityChangedLock = new object();
        /// <summary>
        /// Holds the absolute maximum number of workers that can be managed by a single <see cref="WorkerPool"/>.
        /// </summary>
        public const int MaxCapacity = 1024;
        private static readonly object poolLock = new object();
        private static EventHandler<WorkerEventArgs> workerAdded;
        private static readonly object workerAddedLock = new object();
        private static EventHandler<WorkerEventArgs> workerRemoved;
        private static readonly object workerRemovedLock = new object();
        private static readonly List<Worker> workers = new List<Worker>();
        #endregion
        #region Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="worker"></param>
        /// <returns></returns>
        private static bool AddWorker(Worker worker)
        {
            if (worker == null)
            {
                return false;
            }

            bool result = false;

            // Make sure we're the only one modifying static state...
            lock (WorkerPool.poolLock)
            {
                if (!WorkerPool.workers.Contains(worker))
                {
                    WorkerPool.workers.Add(worker);
                    WorkerPool.OnWorkerAdded(new WorkerEventArgs(worker));
                    result = true;

                }
            }

            return result;
        }
        /// <summary>
        /// Removes a <see cref="Worker"/> from the <see cref="WorkerPool"/>.
        /// </summary>
        /// <param name="worker"></param>
        /// <returns>true if the specified <see cref="Worker"/> was removed successfully, otherwise false.</returns>
        private static bool RemoveWorker(Worker worker)
        {
            if (worker == null)
            {
                return false;
            }
            bool result = false;
            
            // Make sure we're the only one modifying static state...
            lock (WorkerPool.poolLock)
            {
                if (WorkerPool.workers.Contains(worker))
                {
                    result = WorkerPool.workers.Remove(worker);
                    WorkerPool.OnWorkerRemoved(new WorkerEventArgs(worker));
                }
            }

            return result;
        }
        private static void OnCapacityChanged(EventArgs e)
        {
            EventHandler handler;
            lock (WorkerPool.capacityChangedLock)
            {
                handler = WorkerPool.capacityChanged;
            }
            if (handler != null)
            {
                handler(null, e);
            }

            lock (WorkerPool.poolLock)
            {
                int count = WorkerPool.Count;
                int capacity = WorkerPool.Capacity;
                if (count == capacity)
                {
                    // Don't need to adjust anything.
                    return;
                }
                else if (count > capacity)
                {
                    // There are more workers than the new capacity allows for, so we need to shut some down.
                    int toShutdown = count - capacity;

                    while (toShutdown > 0)
                    {
                        var worker = WorkerPool.GetFirstIdleWorker();

                        worker.Shutdown();
                        WorkerPool.RemoveWorker(worker);

                        toShutdown--;
                    }
                }
                else
                {
                    // There are less workers than the new capacity allows for, so we need to create some more.

                    int toCreate = capacity - count;

                    while (toCreate > 0)
                    {
                        Worker worker = new Worker();

                        WorkerPool.AddWorker(worker);

                        toCreate--;
                    }
                }
            }
        }
        private static void OnWorkerAdded(WorkerEventArgs e)
        {
            EventHandler<WorkerEventArgs> handler;
            lock (WorkerPool.workerAddedLock)
            {
                handler = WorkerPool.workerAdded;
            }
            if (handler != null)
            {
                handler(null, e);
            }
        }
        private static void OnWorkerRemoved(WorkerEventArgs e)
        {
            EventHandler<WorkerEventArgs> handler;
            lock (WorkerPool.workerRemovedLock)
            {
                handler = WorkerPool.workerRemoved;
            }
            if (handler != null)
            {
                handler(null, e);
            }
        }
        /// <summary>
        /// Finds the first idle <see cref="Worker"/> in the <see cref="WorkerPool"/>. If there are no idle workers, this method
        /// returns the worker that expects to finish it's current task the soonest.
        /// </summary>
        /// <returns></returns>
        public static Worker GetFirstIdleWorker()
        {
            var workers = WorkerPool.Workers;

            if (workers.Count == 0)
            {
                return null;
            }

            return workers.FirstOrDefault(w => w.Status == WorkerStatus.Idle) ?? (from w in workers
                                                                         orderby w.ActiveTask.EstimatedTimeLeft
                                                                         select w).FirstOrDefault();

        }
        #endregion
        #region Properties
        /// <summary>
        /// Gets or sets the <see cref="Worker"/> capacity of the current <see cref="WorkerPool"/>.
        /// </summary>
        public static int Capacity
        {
            get
            {
                return WorkerPool.capacity;
            }
            set
            {
                WorkerPool.capacity = Math.Max(value, 0);
                WorkerPool.OnCapacityChanged(null);
            }
        }
        /// <summary>
        /// Gets the number of actual <see cref="Workers"/> that are being
        /// supervised.
        /// </summary>
        public static int Count
        {
            get
            {
                return WorkerPool.workers.Count;
            }
        }
        /// <summary>
        /// Gets the number of <see cref="Worker">Workers</see> that are idle.
        /// </summary>
        public static int IdleCount
        {
            get
            {
                throw new NotImplementedException();
            }
        }
        /// <summary>
        /// Gets the number of <see cref="Worker">Workers</see> that are active.
        /// </summary>
        public static int WorkingCount
        {
            get
            {
                throw new NotImplementedException();
            }
        }
        /// <summary>
        /// Gets a collection of the <see cref="Worker"/> objects in the current <see cref="WorkerPool"/>.
        /// </summary>
        public static ReadOnlyCollection<Worker> Workers
        {
            get
            {
                return WorkerPool.workers.AsReadOnly();
            }
        }
        #endregion
    }
}
